/**
 * This file is part of the NatCrackerToolbox.
 * 
 * Copyright (C) 2009 - 2011 Peerialism Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 **/
package se.peerialism.natcrackeremu.data;

import java.io.BufferedReader;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.StringTokenizer;

import com.peerialism.natcracker.common.GatewayDevice;
import com.peerialism.natcracker.common.PortMappingTechnique;
import com.peerialism.natcracker.common.TraversalStrategy;


public class ConnectivityDataManager {

	private static ConnectivityDataManager cdm;

	private final String DEFAULT_NAT_TYPES_DATA = "data/natTypesDistr.dat";
	private final String DEFAULT_NAT_SUCCESS_DATA = "data/natSuccessRateDistr.dat";
	private final Map<Integer, GatewayDevice> typesMap = new HashMap<Integer, GatewayDevice>();
	private final Map<TraversalStrategy, Float> strategiesMap = new HashMap<TraversalStrategy, Float>();
	private List<Integer> typesNumberList;
	private List<Float> typesRatioList;
	final private Random gen;
	int totalTypeCount = 0;

	public static ConnectivityDataManager getInstance(long seed)
	{
		if (cdm == null)
			cdm = new ConnectivityDataManager(seed);
		return cdm;
	}

	private ConnectivityDataManager(long seed) {
		gen = new Random(seed);
		init();
	}

	public ConnectivityDataManager(long seed, String path) {
		gen = new Random(seed);
		File file = new File(path);
		try
		{
			loadNATDataFile(new FileInputStream(file));
		} catch (FileNotFoundException e)
		{
			throw new RuntimeException("File does not exists");
		}
	}

	private void init()
	{
		InputStream is = Thread.currentThread().getContextClassLoader()
		.getResourceAsStream(DEFAULT_NAT_TYPES_DATA);
		loadNATDataFile(is);
		InputStream isS = Thread.currentThread().getContextClassLoader()
		.getResourceAsStream(DEFAULT_NAT_SUCCESS_DATA);
		loadSuccessRateDataFile(isS);
	}

	public void reset()
	{
		typesMap.clear();
		typesNumberList.clear();
		typesRatioList.clear();
	}

	private boolean loadNATDataFile(InputStream is)
	{
		StringBuffer strWriter = new StringBuffer();
		try
		{
			BufferedReader reader = new BufferedReader(
					new InputStreamReader(is));
			while (true)
			{
				String temp = reader.readLine();
				if (temp == null)
				{
					loadTypesData(strWriter.toString());
					return true;
				}
				strWriter.append(temp + "\n");
			}
		} catch (EOFException e)
		{
			return false;
		} catch (FileNotFoundException e)
		{
			return false;
		} catch (IOException e)
		{
			return false;
		}
	}

	private boolean loadSuccessRateDataFile(InputStream is)
	{
		try
		{
			BufferedReader reader = new BufferedReader(
					new InputStreamReader(is));
			while (true)
			{
				String temp = reader.readLine();
				if (temp != null)
					loadSuccessRateRow(temp);
				else
					break;
			}
			return false;
		} catch (EOFException e)
		{
			return false;
		} catch (FileNotFoundException e)
		{
			return false;
		} catch (IOException e)
		{
			return false;
		}
	}

	private void loadSuccessRateRow(String row)
	{
		StringTokenizer tok = new StringTokenizer(row, " \n");
		while (true)
			if (tok.hasMoreTokens())
			{
				String t = tok.nextToken();
				StringTokenizer lineTok = new StringTokenizer(t, "-=\n");
				String str1 = lineTok.nextToken();
				String str2 = lineTok.nextToken();
				String val = lineTok.nextToken();
				Float rate = Float.valueOf(val);
				TraversalStrategy strategy = new TraversalStrategy(
						toMapingTechnique(str1), toMapingTechnique(str2), rate);
				strategiesMap.put(strategy, rate);
			} else
				break;
	}

	public float getStrategySuccessRate(TraversalStrategy str)
	{
		TraversalStrategy strLocal = str;
		if (str.getDownloaderStrategy() == PortMappingTechnique.NONE
				&& str.getUploaderStrategy() == PortMappingTechnique.NONE)
			return 1;
		else if (!strategiesMap.containsKey(str))
			strLocal = new TraversalStrategy(str.getDownloaderStrategy(), str
					.getUploaderStrategy(), 0.0);
		if (strategiesMap.get(strLocal) == null)
			return (float) 0.0;
		else
			return strategiesMap.get(strLocal);
	}

	PortMappingTechnique toMapingTechnique(String st1)
	{
		if (st1.equals("SHP"))
			return PortMappingTechnique.ACQUIRE;
		else if (st1.equals("PRC"))
			return PortMappingTechnique.PREDICT_CONTIGUITY;
		else if (st1.equals("PRP"))
			return PortMappingTechnique.PREDICT_PRESERVATION;
		else
			return PortMappingTechnique.NONE;
	}

	private void loadTypesData(String data)
	{
		StringTokenizer tok = new StringTokenizer(data, " \n");
		typesNumberList = new LinkedList<Integer>();
		while (true)
			if (tok.hasMoreTokens())
			{
				String t = tok.nextToken();
				GatewayDevice devObj = GatewayDevice.createFromString(t);
				Integer num = Integer.parseInt(tok.nextToken());
				typesMap.put(num, devObj);
				totalTypeCount += num;
				typesNumberList.add(num);
			} else
				break;
		Collections.sort(typesNumberList);
		typesRatioList = new ArrayList<Float>();
		float previous = 0;
		for (int i = 0; i < typesNumberList.size(); i++)
		{
			float current = (((float) typesNumberList.get(i)) / totalTypeCount)
			+ previous;
			typesRatioList.add(current);
			previous = current;
		}
	}

	public GatewayDevice getNextGatewayDevice()
	{
		if (typesRatioList == null)
			return null;
		float picked = gen.nextFloat();
		int index = Collections.binarySearch(typesRatioList, picked);
		if (index < 0)
			index = -index;
		return typesMap.get(typesNumberList.get(index - 1));
	}

	public static void main(String[] args)
	{
		ConnectivityDataManager conn = new ConnectivityDataManager(2);

		Map<GatewayDevice, Integer> nums = new HashMap<GatewayDevice, Integer>();
		for (int i = 0; i < 1000; i++)
		{
			GatewayDevice dev = conn.getNextGatewayDevice();
			if (!nums.containsKey(dev))
				nums.put(dev, 0);
			int num = nums.get(dev);
			num++;
			nums.put(dev, num);
		}
		System.out.println("nums: " + nums);
	}
}
